/*****************************************************************************
* File Name: AudioControl.c
* Version 
*
* Description: 
* Description: This file contains the Audio signal path configuration and 
*               processing code
*
* Owner:
* SIRK
*    
* Related Document:
*     
*
* Hardware Dependency:
* CYPRESS BLE Dongle
*
* Code Tested With:
* PSoC Creator    
*
******************************************************************************
* Copyright (2015), Cypress Semiconductor Corporation.
******************************************************************************
* This software is owned by Cypress Semiconductor Corporation (Cypress) and is
* protected by and subject to worldwide patent protection (United States and
* foreign), United States copyright laws and international treaty provisions.
* Cypress hereby grants to licensee a personal, non-exclusive, non-transferable
* license to copy, use, modify, create derivative works of, and compile the
* Cypress Source Code and derivative works for the sole purpose of creating
* custom software in support of licensee product to be used only in conjunction
* with a Cypress integrated circuit as specified in the applicable agreement.
* Any reproduction, modification, translation, compilation, or representation of
* this software except as specified above is prohibited without the express
* written permission of Cypress.
*
* Disclaimer: CYPRESS MAKES NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH
* REGARD TO THIS MATERIAL, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
* Cypress reserves the right to make changes without further notice to the
* materials described herein. Cypress does not assume any liability arising out
* of the application or use of any product or circuit described herein. Cypress
* does not authorize its products for use as critical components in life-support
* systems where a malfunction or failure may reasonably be expected to result in
* significant injury to the user. The inclusion of Cypress' product in a life-
* support systems application implies that the manufacturer assumes all risk of
* such use and in doing so indemnifies Cypress against all charges. Use may be
* limited by and subject to the applicable Cypress software license agreement.
*****************************************************************************/
#include "AudioControl.h"
#include "Interrupts.h"

/*****************************************************************************
* MACRO Definition
*****************************************************************************/
#define ALT_SETTING_ZERO_BANDWIDTH 0
#define IN_UNDERFLOWLIMIT    (AUDIO_SAMPLE_SIZE)

/*RX DMA burst count is set to match with minimum possible transaction size*/
#define RX_DMA_BURSTCOUNT    (80u)
#define IN_DMA_BURSTCOUNT    (1u)

#define PRESERVE_TDS    (1u)

/*****************************************************************************
* Global Variable Declarations
*****************************************************************************/
/*This flag indicates the DMA transaction completion
 *from inTransBuffer to circular buffer.*/
/*Enable this flag by default, so that first DMA gets triggered*/
volatile bool RxDMADone = true;

/*Flag to indicate audio pipe opening and closing from the audio device*/
volatile bool audioPipeOpen = false;

/*audio IN stream (recording) active flag */
bool inPlaying = false;

/* audio IN circular buffer */
uint8 inBuffer[IN_BUFSIZE] = {0};

/*Buffer to hold the uncompressed audio data of each packet*/
uint8 inTransBuffer[IN_TRANS_SIZE];

/* audio IN buffer level pointer - this contains valid data size in circular buffer*/
volatile uint16 inLevel = IN_SENDLIMIT;

/*****************************************************************************
* Local Global Variable Declarations
*****************************************************************************/
/* USB IN circular SRAM memory to IN EP DMA */
static uint8 USBInDmaChan = 0;
/* inTransBuffer  -> IN Circular buffer DMA */
static uint8 rxDmaChan = 0;

/*Circular IN buffer TDs */
static uint8 rxTd[NUM_TDS] = {0};
/* USB IN circular SRAM memory to IN EP TDs */
static uint8 USBInTd[2] = {0};

/* audio IN circular buffer index */
static uint16 inBufIndex = 0;

#ifdef AUDIO_REPEAT_LAST_SAMPLE
/*Buffer to hold the last sent USB sample, this will be used if last sample to be repeated for underflow gaps*/
static uint8 shadowBuffer[AUDIO_SAMPLE_SIZE] = {0};
#endif /*AUDIO_REPEAT_LAST_SAMPLE*/

/*USB audio data buffer for next USB EP polling*/
#if(USBFS_EP_MM == USBFS__EP_DMAAUTO) 
static uint8 inRam[AUDIO_SAMPLE_SIZE] = {0};
#endif

/*******************************************************************************
* Function Name: ConfigureAudioPath
********************************************************************************
* Summary:
*       This function sets up the DMA and starts USB and interrupts
*       to get the PSoC 5 device configured for audio streaming mode
*
* Parameters:
*  None
*
* Return:
*  None
*
* Theory:
*  One TD configured for one input transaction into circular buffer, every trigger for RXDMA will use the next configured TD and load the Circ buffer.
*
* Side Effects:
* None
*
*******************************************************************************/
void ConfigureAudioPath(void)
{
    uint8 index;

    /* Audio sample 320byte Buffer to Circular Buffer DMA configuration*/

    /* Rx DMA Config, 80 byte bursts, one request auto-transfers all bursts for a Td, upper addr of 0 */
    rxDmaChan = RxDMA_DmaInitialize(RX_DMA_BURSTCOUNT, 0, HI16(CYREG_SRAM_DATA_MBASE), HI16(CYREG_SRAM_DATA_MBASE));

    for(index=0; index < NUM_TDS; index++)
    {
        /* Request for a set of TDs from the pool */
        rxTd[index] = CyDmaTdAllocate();
    }

    for(index=0; index < NUM_TDS; index++)
    {
        /* Configure this Td chain as follows:
               *  Loop through all of the TDs, Increment the destination address and the source address */
        CyDmaTdSetConfiguration(rxTd[index], IN_TRANS_SIZE, rxTd[(index+1)%NUM_TDS],
                                TD_INC_SRC_ADR |TD_INC_DST_ADR | RxDMA__TD_TERMOUT_EN);

        /* From Decompressed Transaction buffer to Audio In Circ. buffer */
        CyDmaTdSetAddress(rxTd[index], LO16((uint32)inTransBuffer), LO16((uint32)inBuffer+(IN_TRANS_SIZE*index)));
    }

    /* USB In DMA Config: Entire chain is sent based on a single request from the CPU. Td's are configured later    */
    USBInDmaChan = USBInDMA_DmaInitialize(IN_DMA_BURSTCOUNT, 0, HI16(CYREG_SRAM_DATA_MBASE),
                                            HI16(CYREG_SRAM_DATA_MBASE)); 
    USBInTd[0] = CyDmaTdAllocate();
    USBInTd[1] = CyDmaTdAllocate();

    isr_RxDMADone_StartEx(RxDMADone_Interrupt);
    isr_InDMADone_StartEx(InDMADone_Interrupt);
}

/*******************************************************************************
* Function Name: ProcessAudioIn
********************************************************************************
* Summary:
*        Handle USB audio in data, setup USB DMA and trigger to transfer samples 
*        from SRAM audio circular buffer to SRAM USB endpoint memory.
*        Enable RXDMA channel to load the RX data to circ buffer when occurs.
*
* Parameters:
*  None
*
* Return:
*  None
*
* Theory:
*  On each USB poll for Audio EP, one sample data will be fethed from Circ buffer via InDma and loaded to EP.
*
* Side Effects:
* Incremenets Circ Buffer read pointer inBufIndex
* inPlaying will be set to true once the data transfer to Host is started
*
*******************************************************************************/
void ProcessAudioIn(void)
{
    CyDmaChDisable(USBInDmaChan);

    /* Single contiguous TD */
    CyDmaTdSetConfiguration(USBInTd[0], AUDIO_SAMPLE_SIZE, CY_DMA_DISABLE_TD,
                            TD_INC_SRC_ADR | TD_INC_DST_ADR | USBInDMA__TD_TERMOUT_EN);
    CyDmaTdSetAddress(USBInTd[0], LO16((uint32)inBuffer+inBufIndex), LO16((uint32)inRam));

    CyDmaChSetInitialTd(USBInDmaChan, USBInTd[0]);
    inBufIndex += AUDIO_SAMPLE_SIZE;
    if(inBufIndex == sizeof(inBuffer))
    {
        inBufIndex = 0;
    }

    /* Enable the USB In DMA, don't update the Td as it progresses */
    CyDmaChEnable(USBInDmaChan, PRESERVE_TDS);
    CyDmaChSetRequest(USBInDmaChan, CPU_REQ); /* Start the DMA now */

#ifdef AUDIO_REPEAT_LAST_SAMPLE
    memcpy(shadowBuffer, &inBuffer[inBufIndex], sizeof(shadowBuffer));
#endif /*AUDIO_REPEAT_LAST_SAMPLE*/

    /* Sending of the data on the USB interface is enabled when the interrupt indicates that the buffer has been filled. */
    if(!inPlaying && (inLevel < IN_SENDLIMIT))     //Start the audio pipe only when USB consumes send limit from inBuffer
    {
        inPlaying = true;

        /*Clear Any potential pending DMA requests before starting the DMA channel to transfer data */
        CyDmaChSetRequest(rxDmaChan, CPU_TERM_CHAIN);
        CyDmaChEnable(rxDmaChan, PRESERVE_TDS);

        /* Enable the Rx DMA, initialized back to the first TD */
        CyDmaChSetInitialTd(rxDmaChan, rxTd[IN_SENDLIMIT/IN_TRANS_SIZE]);
        CyDmaChEnable(rxDmaChan, PRESERVE_TDS);
    }
}

/*******************************************************************************
* Function Name: HandleAudioInDisable
********************************************************************************
* Summary:
*       This routine clears the audio IN stream circular buffer and the audio
*       IN endpoint memory location based on the status of the audio IN stream.
*
* Parameters:
*  None
*
* Return:
*  None
*
* Theory:
*  When hosts sends Config request to change the alternate interface, the audio data path need to be disabled as USB is not polling for data.
*  Disable the RX DMA and Clear the buffers, RX data is expected to be dropped after this command from Host
*
* Side Effects:
*  inPlaying will be set to false to indicate that new data to be dropped
*  Circ buffer and corresponding pointers will be reset
*
*******************************************************************************/
void HandleAudioInDisable(void)
{
    if(ALT_SETTING_ZERO_BANDWIDTH == USBFS_GetInterfaceSetting(AUDIO_IN_INTERFACE_NO))
    {
        if(inPlaying)
        {
            /* Terminate TD chain & Stop/Disable DMA - Needed to reset to start of chain */
            CyDmaChSetRequest(rxDmaChan, CPU_TERM_CHAIN);

            inLevel = IN_SENDLIMIT;
            inBufIndex = 0;
            inPlaying = false;

            memset(inBuffer, 0, sizeof(inBuffer));
            memset(inRam, 0, sizeof(inRam));
        }
    }
}

/*******************************************************************************
* Function Name: Trigger_rXDMA
********************************************************************************
* Summary:
*       This routine sets the request to rxDMA that transfers data from RX buffer to circular buffer. 
*       This API should be called after the RX packet is ready. Caller is expected to wait until current DMA transaction is completed.
*       This function also check for Circ buffer over flow condition and drops new data if no available space in the buffer.
*
* Parameters:
*  None
*
* Return:
*  None
*
* Theory:
*  None
*
* Side Effects:
*  RxDMADone will be set to false which needs to be set to true once the active DMA is completed
*  If no space available in the buffer DMA will not be triggered.
*
*******************************************************************************/
void Trigger_rXDMA(void)
{
    /*Wait till previous DMA is complete*/
    if((RxDMADone)&&(inLevel < ((int16)(sizeof(inBuffer)-IN_TRANS_SIZE))))
    {
        RxDMADone = false;
        CyDmaChEnable(rxDmaChan, PRESERVE_TDS);
        CyDmaChSetRequest(rxDmaChan, CPU_REQ); /* Start the DMA now */
    }
}

/*******************************************************************************
* Function Name: HandleUnderflow
********************************************************************************
* Summary:
*       This routine monitors the amount of valid data available in the inBuffer
*       and handles the underflow condition when the data in inBufer is less
*       then IN_UNDERFLOWLIMIT 
* Parameters:
*  None
*
* Return:
*  None
*
* Theory:
*  USB ISOC EP expects data on every polling, if no valid data available in the buffer, this function creates virtual data and sends it to Host
*
* Side Effects:
*  RxDMADone will be set to false which needs to be set to true once the active DMA is completed
*  If no space available in the buffer DMA will not be triggered.
*
*******************************************************************************/
void HandleUnderflow(void)
{
    isr_RxDMADone_Disable();
    if(inLevel < IN_UNDERFLOWLIMIT)
    {
        /* Move back the read pointer to send handle underflow */
        if(inBufIndex >= IN_UNDERFLOWLIMIT)
        {
            inBufIndex -= IN_UNDERFLOWLIMIT;
        }
        else
        {
            inBufIndex = sizeof(inBuffer) - (IN_UNDERFLOWLIMIT - inBufIndex);
        }
        inLevel += IN_UNDERFLOWLIMIT;

#ifdef AUDIO_REPEAT_LAST_SAMPLE
        if(audioPipeOpen)
        {
            /* Copy previous sample into the buffer to handle underflow */
            memcpy(&inBuffer[inBufIndex], shadowBuffer, IN_UNDERFLOWLIMIT);
        }
        else
#endif /*AUDIO_REPEAT_LAST_SAMPLE*/
        {
            /* Fill zeros to handle underflow */
            memset(&inBuffer[inBufIndex], 0, IN_UNDERFLOWLIMIT);
        }
    }
    isr_RxDMADone_Enable();
}

/*******************************************************************************
* Function Name: ServiceUSB
********************************************************************************
* Summary: 
*          When the USB configuration is changed, this routine reinitializes
*          USB audio endpoints as required by the application.
*
* Parameters:
*  None
*
* Return:
*  None
*
* Theory:
*  None
*
* Side Effects:
*  None
*
*******************************************************************************/
void ServiceUSB(void)
{
    /* USBFS_IsConfigurationChanged() is a clear on read status update therefore, only one read of 
     * USBFS_IsConfigurationChanged() should ever exist in user code */
    if(USBFS_IsConfigurationChanged())
    {
        /* Arming the audio in EP if it is not zero bandwidth alt setting */
        if((ALT_SETTING_ZERO_BANDWIDTH != USBFS_GetInterfaceSetting(AUDIO_IN_INTERFACE_NO)) && 
            ((CY_GET_REG8(USBFS_SIE_EP1_CR0_PTR + ((AUDIO_IN_ENDPOINT - USBFS_EP1) << USBFS_EPX_CNTX_ADDR_SHIFT))
                & USBFS_MODE_MASK) == USBFS_MODE_NAK_IN_OUT))
        {
            /*On Host config set to active, Set the DMA flag so that first DMA gets triggered*/
            RxDMADone = true;

            /* Init DMA configurations for audio IN EP */
            memset(inRam, 0, sizeof(inRam));
            USBFS_LoadInEP(AUDIO_IN_ENDPOINT, inRam, sizeof(inRam));
            /* Pre-arm first audio IN request */
            USBFS_LoadInEP(AUDIO_IN_ENDPOINT, USBFS_NULL, sizeof(inRam));
        }
    }
}

/* [] END OF FILE */
